package com.pig4cloud.pig.auth.support.password;

import com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationProvider;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;

import java.util.Map;

/**
 * OAuth2 资源所有者密码认证提供者
 * <p>
 * 该类继承自 OAuth2ResourceOwnerBaseAuthenticationProvider，
 * 负责处理密码模式（Resource Owner Password Credentials Grant）的认证逻辑。
 * 
 * 主要功能：
 * 1. 验证客户端是否支持密码授权模式
 * 2. 构建用户名密码认证令牌
 * 3. 委托 AuthenticationManager 进行实际的用户认证
 * 4. 生成访问令牌和刷新令牌
 * 
 * 认证流程：
 * 1. 接收 OAuth2ResourceOwnerPasswordAuthenticationToken
 * 2. 验证客户端是否支持 password grant type
 * 3. 从请求中提取 username 和 password
 * 4. 使用 AuthenticationManager 认证用户
 * 5. 生成并返回 OAuth2 令牌
 *
 * @author lengleng
 * @author jumuning
 * @date 2025/05/30
 * @since 0.2.3
 */
public class OAuth2ResourceOwnerPasswordAuthenticationProvider
		extends OAuth2ResourceOwnerBaseAuthenticationProvider<OAuth2ResourceOwnerPasswordAuthenticationToken> {

	/**
	 * 日志记录器
	 */
	private static final Logger LOGGER = LogManager.getLogger(OAuth2ResourceOwnerPasswordAuthenticationProvider.class);

	/**
	 * 使用提供的参数构造一个 OAuth2ResourceOwnerPasswordAuthenticationProvider
	 * <p>
	 * 构造函数传递所有必要的依赖项给父类，这些依赖项将用于：
	 * - authenticationManager：执行实际的用户名密码认证
	 * - authorizationService：管理 OAuth2 授权信息
	 * - tokenGenerator：生成访问令牌和刷新令牌
	 * 
	 * @param authenticationManager 认证管理器，用于验证用户名密码
	 * @param authorizationService 授权服务，用于存储和管理授权信息
	 * @param tokenGenerator 令牌生成器，用于生成 OAuth2 令牌
	 * @since 0.2.3
	 */
	public OAuth2ResourceOwnerPasswordAuthenticationProvider(AuthenticationManager authenticationManager,
			OAuth2AuthorizationService authorizationService,
			OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
		super(authenticationManager, authorizationService, tokenGenerator);
	}

	/**
	 * 构建用户名密码认证令牌
	 * <p>
	 * 从请求参数中提取用户名和密码，并创建 UsernamePasswordAuthenticationToken。
	 * 这个令牌将被传递给 AuthenticationManager 进行实际的认证。
	 * 
	 * @param reqParameters 请求参数映射，必须包含：
	 *                      - username: 用户名
	 *                      - password: 密码
	 * @return 用户名密码认证令牌，用于后续认证
	 */
	@Override
	public UsernamePasswordAuthenticationToken buildToken(Map<String, Object> reqParameters) {
		// 从请求参数中提取用户名和密码
		String username = (String) reqParameters.get(OAuth2ParameterNames.USERNAME);
		String password = (String) reqParameters.get(OAuth2ParameterNames.PASSWORD);
		// 创建 Spring Security 的标准用户名密码认证令牌
		return new UsernamePasswordAuthenticationToken(username, password);
	}

	/**
	 * 判断是否支持指定的认证类型
	 * <p>
	 * 该方法检查传入的认证类型是否为 OAuth2ResourceOwnerPasswordAuthenticationToken
	 * 或其子类。这是 Spring Security 认证链的标准机制。
	 * 
	 * @param authentication 待验证的认证类型
	 * @return 如果支持该认证类型则返回 true，否则返回 false
	 */
	@Override
	public boolean supports(Class<?> authentication) {
		boolean supports = OAuth2ResourceOwnerPasswordAuthenticationToken.class.isAssignableFrom(authentication);
		// 记录调试日志，便于排查认证链问题
		LOGGER.debug("supports authentication=" + authentication + " returning " + supports);
		return supports;
	}

	/**
	 * 检查客户端是否支持密码授权模式
	 * <p>
	 * 根据 OAuth2 规范，不是所有客户端都允许使用密码模式。
	 * 客户端必须在注册时明确指定支持 password grant type。
	 * 这是一种安全措施，确保只有可信任的客户端才能处理用户密码。
	 * 
	 * @param registeredClient 已注册的客户端信息
	 * @throws OAuth2AuthenticationException 当客户端不支持密码授权模式时抛出异常
	 */
	@Override
	public void checkClient(RegisteredClient registeredClient) {
		// 防御性编程：确保客户端不为空
		assert registeredClient != null;
		// 检查客户端是否支持密码授权类型
		if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.PASSWORD)) {
			// 抛出未授权客户端异常
			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
		}
	}

}
